package com.asiainfo.dao;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.session.Configuration;

public class MapperScan {

	private final Map<String, Long> mapperLastModifiedRecord = new HashMap<String, Long>();

	private Configuration configuration;
	private ClassLoader cloader;
	private String scanMappersDir;
	private String classpath;

	public void loadMappers(Configuration configuration) {
		try {
			this.configuration = configuration;
			this.cloader = Thread.currentThread().getContextClassLoader();
			this.classpath = new File(MapperScan.class.getResource("/").getPath()).getCanonicalPath().replace('\\', '/');
			this.scanMappersDir = "mappers/" + configuration.getEnvironment().getId();
			dynamicLoadMappers();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public synchronized void dynamicLoadMappers() {
		Set<String> mappers = scanModifiedFiles();
		try {
			for (String mapper : mappers) {
				loadMapper(mapper);
				System.err.println("=========================== " + mapper + "热加载完成 =================================");
			}
			recordMapperLastModified();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void recordMapperLastModified() {
		try {
			synchronized (mapperLastModifiedRecord) {
				Set<String> mappers = getMappers();
				if (mappers == null || mappers.size() == 0)
					return;
				for (String mapperKey : mappers) {
					mapperLastModifiedRecord.put(mapperKey, Long.valueOf(new File(classpath + "/" + mapperKey).lastModified()));
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private Set<String> getMappers() throws NoSuchFieldException, IllegalAccessException {
		Field loadedResourcesField = configuration.getClass().getDeclaredField("loadedResources");
		loadedResourcesField.setAccessible(true);
		return (Set<String>) loadedResourcesField.get(configuration);
	}

	private Set<String> scanModifiedFiles() {
		String baseMapperDir = classpath + "/" + scanMappersDir;
		List<String> mappers = scanAllMapperXml(baseMapperDir);
		Set<String> ret = new HashSet<String>();
		for (String mapper : mappers) {
			String mapperKey = mapper.substring(classpath.length() + 1);
			if (!mapperLastModifiedRecord.containsKey(mapperKey)) {
				ret.add(mapperKey);
				continue;
			}
			if (mapperLastModifiedRecord.get(mapperKey).longValue() != new File(mapper).lastModified()) {
				ret.add(mapperKey);
			}
		}
		return ret;
	}

	private void loadMapper(String resource) throws Exception {
		InputStream inputStream = cloader.getResourceAsStream(resource);
		String[] mapFieldNames = new String[] { "mappedStatements", "caches", "resultMaps", "parameterMaps", "keyGenerators", "sqlFragments" };
		for (String fieldName : mapFieldNames) {
			Field field = configuration.getClass().getDeclaredField(fieldName);
			field.setAccessible(true);
			Map map = (Map) field.get(configuration);
			if (!(map instanceof StrictMap)) {
				Map newMap = new StrictMap<Object>(StringUtils.capitalize(fieldName) + " collection");
				for (Object key : map.keySet()) {
					try {
						newMap.put(key, map.get(key));
					} catch (IllegalArgumentException ex) {
						newMap.put(key, ex.getMessage());
					}
				}
				field.set(configuration, newMap);
			}
		}
		Set<String> mappers = getMappers();
		mappers.remove(resource);
		XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
		xmlMapperBuilder.parse();
	}

	private static class StrictMap<V> extends HashMap<String, V> {

		private static final long serialVersionUID = -4950446264854982944L;
		private final String name;

		public StrictMap(String name, int initialCapacity, float loadFactor) {
			super(initialCapacity, loadFactor);
			this.name = name;
		}

		public StrictMap(String name, int initialCapacity) {
			super(initialCapacity);
			this.name = name;
		}

		public StrictMap(String name) {
			super();
			this.name = name;
		}

		public StrictMap(String name, Map<String, ? extends V> m) {
			super(m);
			this.name = name;
		}

		@SuppressWarnings("unchecked")
		public V put(String key, V value) {
			if (key.contains(".")) {
				final String shortKey = getShortName(key);
				if (super.get(shortKey) == null) {
					super.put(shortKey, value);
				} else {
					super.put(shortKey, (V) new Ambiguity(shortKey));
				}
			}
			return super.put(key, value);
		}

		public V get(Object key) {
			V value = super.get(key);
			if (value == null) {
				throw new IllegalArgumentException(name + " does not contain value for " + key);
			}
			if (value instanceof Ambiguity) {
				throw new IllegalArgumentException(((Ambiguity) value).getSubject() + " is ambiguous in " + name + " (try using the full name including the namespace, or rename one of the entries)");
			}
			return value;
		}

		private String getShortName(String key) {
			final String[] keyParts = key.split("\\.");
			return keyParts[keyParts.length - 1];
		}

		protected static class Ambiguity {
			final private String subject;

			public Ambiguity(String subject) {
				this.subject = subject;
			}

			public String getSubject() {
				return subject;
			}
		}
	}

	private static List<String> scanAllMapperXml(String path) {
		List<String> filePaths = new ArrayList<String>();
		try {
			LinkedList<File> list = new LinkedList<File>();
			File dir = new File(path);
			if(!dir.exists()){
				return filePaths;
			}
			File[] file = dir.listFiles();
			for (int i = 0; i < file.length; i++) {
				if (file[i].isDirectory()) {
					// 把第一层的目录，全部放入链表
					list.add(file[i]);
				} else if (file[i].getName().toLowerCase().endsWith(".xml")) {
					filePaths.add(file[i].getCanonicalPath().replace("\\", "/"));
				}
			}
			// 循环遍历链表
			while (!list.isEmpty()) {
				// 把链表的第一个记录删除
				File tmp = list.removeFirst();
				// 如果删除的目录是一个路径的话
				if (tmp.isDirectory()) {
					// 列出这个目录下的文件到数组中
					file = tmp.listFiles(new FileFilter() {
						public boolean accept(File pathname) {
							return pathname.getName().toLowerCase().endsWith(".xml");
						}
					});
					if (file == null) {// 空目录
						continue;
					}
					// 遍历文件数组
					for (int i = 0; i < file.length; ++i) {
						if (file[i].isDirectory()) {
							// 如果遍历到的是目录，则将继续被加入链表
							list.add(file[i]);
						}
						filePaths.add(file[i].getCanonicalPath().replace("\\", "/"));
					}
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return filePaths;
	}

}
